package com.whcoding.task.xxl.job.task.base;

import com.google.common.collect.Lists;

import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * 数据重试控制中心
 * @author whcoding
 * @description 数据重试控制中心
 * @date 2023/4/06 9:10
 */
public class DelayControl<T> {

    /**
     * 维护任务的已经重试的次数   任务失败时add方法插入 成功时remove删除  最终失败insertToDB删除
     */
    private Map<List<T>, Integer> taskRetryMapping = new ConcurrentHashMap();
    /**
     * 按照延迟时间分组  key为第几组从0开始  任务失败时add方法插入 get获取任务时删除
     */
    private Map<Integer, Queue<List<T>>> queueMap = new ConcurrentHashMap();
    /**
     * 维护逻辑同上
     */
    private Map<List<T>, Long> queueTime = new ConcurrentHashMap();
    /**
     * 失败任务的延迟处理时间配置(秒) 配置来自Tasker
     */
    private Integer[] retryDelaySeconds;

    /**
     * tasker
     */
    private BaseTasker tasker;

    /**
     *
     * @param tasker
     */
    DelayControl(BaseTasker<T> tasker) {
        for (int i = 0; i < tasker.getSetting().getRetryDelaySeconds().length; i++) {
            queueMap.put(i, new ConcurrentLinkedQueue());
        }
        this.retryDelaySeconds = tasker.getSetting().getRetryDelaySeconds();
        this.tasker = tasker;
    }

    /**
     * 对外提供的查询任务已经的重试次数
     *
     * @param data 任务数据
     * @return java.lang.Integer
     * @description 对外提供的查询任务已经的重试次数
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    public Integer getRetryTimes(List<T> data) {
        Integer times = taskRetryMapping.get(data);
        return null == times ? 0 : times + 1;
    }

    /**
     * 获取到期的队列
     *
     * @param
     * @return java.util.List
     * @description 获取到期的队列
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    public List<List<T>> get() {
        List<List<T>> result = Lists.newArrayList();

        for (Queue<List<T>> queue : queueMap.values()) {
            List<List<T>> beginData = getBeginData(queue);
            if (beginData.isEmpty()) {
                continue;
            }
            result.addAll(beginData);
        }

        return result;
    }

    /**
     * 获取已经到期的任务数据
     *
     * @param queue 延迟任务队列
     * @return java.util.List
     * @description 获取已经到期的任务数据
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    private List<List<T>> getBeginData(Queue<List<T>> queue) {
        List<List<T>> result = Lists.newCopyOnWriteArrayList();
        int size = queue.size();
        for (int i = 0; i < size; i++) {
            List<T> peek = queue.peek();
            //如果该队列中第一个的时间没到，那么后续就不用看了，肯定也没到
            if (queueTime.get(peek) > System.currentTimeMillis()) {
                break;
            }
            if (null != peek) {
                result.add(peek);
            }
            deleteFromQueue(peek);
        }
        return result;
    }

    /**
     * 新加入一个待延迟处理的任务
     *
     * @param data 任务数据
     * @param e    抛出的异常
     * @return void
     * @description 新加入一个待延迟处理的任务
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    public void add(List<T> data, Exception e) {
        //维护已经重试的次数
        updRetryTimes(data);
        //重试次数没到delaySecond.length则继续重试，否则报错入库
        if (taskRetryMapping.get(data) >= retryDelaySeconds.length) {
            insertToErrorTable(data, e);
            taskRetryMapping.remove(data);
            return;
        }
        //将该任务加到队列中
        insertToQueue(data);
    }

    /**
     * 将成功的任务从延迟中心中移除
     *
     * @param data 任务数据
     * @return void
     * @description 将成功的任务从延迟中心中移除
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    public void remove(List<T> data) {
        deleteFromQueue(data);
        taskRetryMapping.remove(data);
    }

    /**
     * 失败任务入库
     *
     * @param data 任务数据
     * @param e    抛出的异常
     * @return void
     * @description 重试次数到了以后入库
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    private void insertToErrorTable(List<T> data, Exception e) {
        ErrorDataInfo errorDataInfo= new ErrorDataInfo();
        errorDataInfo
                .setE(e)
                .setList(data)
                .setRetryTimes(taskRetryMapping.get(data));
        tasker.errorDataCallback(errorDataInfo);
    }

    /**
     * 维护queueMap和queueTime-新增
     *
     * @param data
     * @return void
     * @description 维护queueMap和queueTime-新增
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    private void insertToQueue(List<T> data) {
        //这个任务已经重试几次了
        int times = taskRetryMapping.get(data);
        //更新其任务执行时间
        queueTime.put(data, System.currentTimeMillis() + retryDelaySeconds[times] * 1000);
        //拿到对应的队列
        Queue<List<T>> ts = queueMap.get(times);
        //待处理的任务加进去
        ts.offer(data);
        //将其从上次重试的队列中移除，如果不是第一次的话
        if (times == 0) {
            return;
        }
        queueMap.get(times - 1).remove(data);
    }

    /**
     * 维护queueMap和queueTime-删除
     *
     * @param data 任务数据
     * @return void
     * @description 维护queueMap和queueTime-删除
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    private void deleteFromQueue(List<T> data) {
        Integer times = taskRetryMapping.get(data);
        queueMap.get(times).remove(data);
        queueTime.remove(data);
    }

    /**
     * 更新任务重试次数
     *
     * @param data 任务数据
     * @return void
     * @description
     * @author whcoding
     * @date 2023/4/06 9:10
     */
    private void updRetryTimes(List<T> data) {
        Integer times = taskRetryMapping.get(data);
        if (null == times) {
            taskRetryMapping.put(data, 0);
            return;
        }
        taskRetryMapping.put(data, times + 1);
    }

}
